From fd0a290d77d5aff6b39706375de2fdca811e04e1 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 5 May 2019 23:53:02 +0000 Subject: [PATCH] popover: Bring back the beak Bring back the code that draws an arrow. --- gtk/gtkpopover.c | 466 ++++++++++++++++++++++- gtk/theme/Adwaita/_common.scss | 20 +- gtk/theme/Adwaita/gtk-contained-dark.css | 8 +- gtk/theme/Adwaita/gtk-contained.css | 8 +- 4 files changed, 480 insertions(+), 22 deletions(-) diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c index 5b8ebfd4a6..f684ca809c 100644 --- a/gtk/gtkpopover.c +++ b/gtk/gtkpopover.c @@ -124,6 +124,21 @@ #include "gdk/gdkeventsprivate.h" #include "gtkpointerfocusprivate.h" #include "gtkcssnodeprivate.h" +#include "gtksnapshot.h" + +#include "gtkrender.h" +#include "gtkstylecontextprivate.h" +#include "gtkroundedboxprivate.h" +#include "gsk/gskroundedrectprivate.h" + +#ifdef GDK_WINDOWING_WAYLAND +#include "wayland/gdkwayland.h" +#endif + +#define TAIL_GAP_WIDTH 24 +#define TAIL_HEIGHT 12 + +#define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM) static GListStore *popover_list = NULL; @@ -141,6 +156,10 @@ typedef struct { gboolean modal; GtkWidget *contents_widget; + GtkCssNode *arrow_node; + + GdkRectangle final_rect; + GtkPositionType final_position; } GtkPopoverPrivate; enum { @@ -364,6 +383,42 @@ surface_event (GdkSurface *surface, return TRUE; } +static void +surface_moved_to_rect (GdkSurface *surface, + GdkRectangle *flipped_rect, + GdkRectangle *final_rect, + gboolean flipped_x, + gboolean flipped_y, + GtkWidget *widget) +{ + GtkPopover *popover = GTK_POPOVER (widget); + GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + + priv->final_rect = *final_rect; + + switch (priv->position) + { + case GTK_POS_LEFT: + priv->final_position = flipped_x ? GTK_POS_RIGHT : GTK_POS_LEFT; + break; + case GTK_POS_RIGHT: + priv->final_position = flipped_x ? GTK_POS_LEFT : GTK_POS_RIGHT; + break; + case GTK_POS_TOP: + priv->final_position = flipped_y ? GTK_POS_BOTTOM : GTK_POS_TOP; + break; + case GTK_POS_BOTTOM: + priv->final_position = flipped_y ? GTK_POS_TOP : GTK_POS_BOTTOM; + break; + default: + g_assert_not_reached (); + break; + } + + if (priv->final_position != priv->position) + gtk_widget_queue_allocate (widget); +} + static void measure_contents (GtkGizmo *gizmo, GtkOrientation orientation, @@ -435,16 +490,29 @@ add_actions (GtkPopover *popover) g_object_unref (actions); } +static void +node_style_changed_cb (GtkCssNode *node, + GtkCssStyleChange *change, + GtkWidget *widget) +{ + if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_SIZE)) + gtk_widget_queue_resize (widget); + else + gtk_widget_queue_draw (widget); +} + static void gtk_popover_init (GtkPopover *popover) { GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + GtkWidget *widget = GTK_WIDGET (popover); GtkEventController *controller; GtkStyleContext *context; gtk_widget_set_has_surface (GTK_WIDGET (popover), TRUE); priv->position = GTK_POS_TOP; + priv->final_position = GTK_POS_TOP; priv->modal = TRUE; controller = gtk_event_controller_key_new (); @@ -453,6 +521,15 @@ gtk_popover_init (GtkPopover *popover) g_signal_connect_swapped (controller, "key-pressed", G_CALLBACK (gtk_popover_key_pressed), popover); gtk_widget_add_controller (GTK_WIDGET (popover), controller); + priv->arrow_node = gtk_css_node_new (); + gtk_css_node_set_name (priv->arrow_node, I_("arrow")); + gtk_css_node_set_parent (priv->arrow_node, gtk_widget_get_css_node (widget)); + gtk_css_node_set_state (priv->arrow_node, + gtk_css_node_get_state (gtk_widget_get_css_node (widget))); + g_signal_connect_object (priv->arrow_node, "style-changed", + G_CALLBACK (node_style_changed_cb), popover, 0); + g_object_unref (priv->arrow_node); + priv->contents_widget = gtk_gizmo_new ("contents", measure_contents, allocate_contents, @@ -487,6 +564,7 @@ gtk_popover_realize (GtkWidget *widget) g_signal_connect_swapped (priv->surface, "size-changed", G_CALLBACK (surface_size_changed), widget); g_signal_connect (priv->surface, "render", G_CALLBACK (surface_render), widget); g_signal_connect (priv->surface, "event", G_CALLBACK (surface_event), widget); + g_signal_connect (priv->surface, "moved-to-rect", G_CALLBACK (surface_moved_to_rect), widget); GTK_WIDGET_CLASS (gtk_popover_parent_class)->realize (widget); @@ -508,6 +586,7 @@ gtk_popover_unrealize (GtkWidget *widget) g_signal_handlers_disconnect_by_func (priv->surface, surface_size_changed, widget); g_signal_handlers_disconnect_by_func (priv->surface, surface_render, widget); g_signal_handlers_disconnect_by_func (priv->surface, surface_event, widget); + g_signal_handlers_disconnect_by_func (priv->surface, surface_moved_to_rect, widget); gdk_surface_set_widget (priv->surface, NULL); g_clear_object (&priv->surface); @@ -660,6 +739,291 @@ gtk_popover_constructed (GObject *object) g_object_unref (object); } +static void +gtk_popover_get_gap_coords (GtkPopover *popover, + gint *initial_x_out, + gint *initial_y_out, + gint *tip_x_out, + gint *tip_y_out, + gint *final_x_out, + gint *final_y_out) +{ + GtkWidget *widget = GTK_WIDGET (popover); + GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + GdkRectangle rect = { 0 }; + gint base, tip, tip_pos; + gint initial_x, initial_y; + gint tip_x, tip_y; + gint final_x, final_y; + GtkPositionType pos; + gint border_radius; + GtkStyleContext *context; + GtkBorder border; + int popover_width, popover_height; + + popover_width = gtk_widget_get_allocated_width (widget); + popover_height = gtk_widget_get_allocated_height (widget); + + gtk_widget_get_surface_allocation (priv->relative_to, &rect); + if (priv->has_pointing_to) + { + rect.x += priv->pointing_to.x; + rect.y += priv->pointing_to.y; + rect.width = priv->pointing_to.width; + rect.height = priv->pointing_to.height; + } + + rect.x -= priv->final_rect.x; + rect.y -= priv->final_rect.y; + + context = gtk_widget_get_style_context (priv->contents_widget); + + pos = priv->final_position; + + gtk_style_context_get_border (context, &border); + gtk_style_context_get (context, + GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius, + NULL); + + if (pos == GTK_POS_BOTTOM || pos == GTK_POS_RIGHT) + { + tip = 0; + base = TAIL_HEIGHT + border.top; + } + else if (pos == GTK_POS_TOP) + { + base = popover_height - border.bottom - TAIL_HEIGHT; + tip = popover_height; + } + else if (pos == GTK_POS_LEFT) + { + base = popover_width - border.right - TAIL_HEIGHT; + tip = popover_width; + } + else + g_assert_not_reached (); + + if (POS_IS_VERTICAL (pos)) + { + tip_pos = rect.x + (rect.width / 2); + initial_x = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2, + border_radius, + popover_width - TAIL_GAP_WIDTH - border_radius); + initial_y = base; + + tip_x = CLAMP (tip_pos, 0, popover_width); + tip_y = tip; + + final_x = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2, + border_radius + TAIL_GAP_WIDTH, + popover_width - border_radius); + final_y = base; + } + else + { + tip_pos = rect.y + (rect.height / 2); + + initial_x = base; + initial_y = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2, + border_radius, + popover_height - TAIL_GAP_WIDTH - border_radius); + + tip_x = tip; + tip_y = CLAMP (tip_pos, 0, popover_height); + + final_x = base; + final_y = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2, + border_radius + TAIL_GAP_WIDTH, + popover_height - border_radius); + } + + *initial_x_out = initial_x; + *initial_y_out = initial_y; + + *tip_x_out = tip_x; + *tip_y_out = tip_y; + + *final_x_out = final_x; + *final_y_out = final_y; +} + +static void +get_margin (GtkWidget *widget, + GtkBorder *border) +{ + GtkStyleContext *context; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_get_margin (context, border); +} +static void +gtk_popover_get_rect_for_size (GtkPopover *popover, + int popover_width, + int popover_height, + GdkRectangle *rect) +{ + GtkWidget *widget = GTK_WIDGET (popover); + int x, y, w, h; + GtkBorder margin; + + get_margin (widget, &margin); + + x = 0; + y = 0; + w = popover_width; + h = popover_height; + + x += MAX (TAIL_HEIGHT, margin.left); + y += MAX (TAIL_HEIGHT, margin.top); + w -= x + MAX (TAIL_HEIGHT, margin.right); + h -= y + MAX (TAIL_HEIGHT, margin.bottom); + + rect->x = x; + rect->y = y; + rect->width = w; + rect->height = h; +} + +static void +gtk_popover_get_rect_coords (GtkPopover *popover, + int *x_out, + int *y_out, + int *w_out, + int *h_out) +{ + GtkWidget *widget = GTK_WIDGET (popover); + GdkRectangle rect; + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + gtk_popover_get_rect_for_size (popover, allocation.width, allocation.height, &rect); + + *x_out = rect.x; + *y_out = rect.y; + *w_out = rect.width; + *h_out = rect.height; +} + +static void +gtk_popover_apply_tail_path (GtkPopover *popover, + cairo_t *cr) +{ + GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + gint initial_x, initial_y; + gint tip_x, tip_y; + gint final_x, final_y; + GtkStyleContext *context; + GtkBorder border; + + if (!priv->relative_to) + return; + + context = gtk_widget_get_style_context (priv->contents_widget); + gtk_style_context_get_border (context, &border); + + cairo_set_line_width (cr, 1); + gtk_popover_get_gap_coords (popover, + &initial_x, &initial_y, + &tip_x, &tip_y, + &final_x, &final_y); + + cairo_move_to (cr, initial_x, initial_y); + cairo_line_to (cr, tip_x, tip_y); + cairo_line_to (cr, final_x, final_y); +} + +static void +gtk_popover_fill_border_path (GtkPopover *popover, + cairo_t *cr) +{ + GtkWidget *widget = GTK_WIDGET (popover); + GtkAllocation allocation; + GtkStyleContext *context; + int x, y, w, h; + GskRoundedRect box; + + context = gtk_widget_get_style_context (widget); + gtk_widget_get_allocation (widget, &allocation); + + cairo_set_source_rgba (cr, 0, 0, 0, 1); + + gtk_popover_apply_tail_path (popover, cr); + cairo_close_path (cr); + cairo_fill (cr); + + gtk_popover_get_rect_coords (popover, &x, &y, &w, &h); + + gtk_rounded_boxes_init_for_style (&box, + NULL, NULL, + gtk_style_context_lookup_style (context), + x, y, w, h); + gsk_rounded_rect_path (&box, cr); + cairo_fill (cr); +} + +static void +gtk_popover_update_shape (GtkPopover *popover) +{ + GtkWidget *widget = GTK_WIDGET (popover); + cairo_surface_t *cairo_surface; + cairo_region_t *region; + GdkSurface *surface; + cairo_t *cr; + +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget))) + return; +#endif + + surface = gtk_widget_get_surface (widget); + cairo_surface = + gdk_surface_create_similar_surface (surface, + CAIRO_CONTENT_COLOR_ALPHA, + gdk_surface_get_width (surface), + gdk_surface_get_height (surface)); + + cr = cairo_create (cairo_surface); + gtk_popover_fill_border_path (popover, cr); + cairo_destroy (cr); + + region = gdk_cairo_region_create_from_surface (cairo_surface); + cairo_surface_destroy (cairo_surface); + + gtk_widget_input_shape_combine_region (widget, region); + cairo_region_destroy (region); +} + +static gint +get_border_radius (GtkWidget *widget) +{ + GtkStyleContext *context; + gint border_radius; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_get (context, + GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius, + NULL); + return border_radius; +} + +static gint +get_minimal_size (GtkPopover *popover, + GtkOrientation orientation) +{ + GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + GtkPositionType pos; + gint minimal_size; + + minimal_size = 2 * get_border_radius (GTK_WIDGET (popover)); + pos = priv->position; + + if ((orientation == GTK_ORIENTATION_HORIZONTAL && POS_IS_VERTICAL (pos)) || + (orientation == GTK_ORIENTATION_VERTICAL && !POS_IS_VERTICAL (pos))) + minimal_size += TAIL_GAP_WIDTH; + + return minimal_size; +} + static void gtk_popover_measure (GtkWidget *widget, GtkOrientation orientation, @@ -671,11 +1035,22 @@ gtk_popover_measure (GtkWidget *widget, { GtkPopover *popover = GTK_POPOVER (widget); GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + int minimal_size; + + if (for_size >= 0) + for_size -= TAIL_HEIGHT; gtk_widget_measure (priv->contents_widget, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); + + minimal_size = get_minimal_size (popover, orientation); + *minimum = MAX (*minimum, minimal_size); + *natural = MAX (*natural, minimal_size); + + *minimum += TAIL_HEIGHT; + *natural += TAIL_HEIGHT; } static void @@ -686,13 +1061,40 @@ gtk_popover_size_allocate (GtkWidget *widget, { GtkPopover *popover = GTK_POPOVER (widget); GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + GtkAllocation child_alloc; if (priv->surface) gtk_popover_move_resize (popover); - gtk_widget_size_allocate (priv->contents_widget, - &(GtkAllocation) { 0, 0, width, height }, - baseline); + switch (priv->final_position) + { + case GTK_POS_TOP: + child_alloc.x = TAIL_HEIGHT / 2; + child_alloc.y = 0; + break; + case GTK_POS_BOTTOM: + child_alloc.x = TAIL_HEIGHT / 2; + child_alloc.y = TAIL_HEIGHT; + break; + case GTK_POS_LEFT: + child_alloc.x = 0; + child_alloc.y = TAIL_HEIGHT / 2; + break; + case GTK_POS_RIGHT: + child_alloc.x = TAIL_HEIGHT; + child_alloc.y = TAIL_HEIGHT / 2; + break; + default: + break; + } + + child_alloc.width = width - TAIL_HEIGHT; + child_alloc.height = height - TAIL_HEIGHT; + + gtk_widget_size_allocate (priv->contents_widget, &child_alloc, baseline); + + if (priv->surface) + gtk_popover_update_shape (popover); } static void @@ -701,8 +1103,53 @@ gtk_popover_snapshot (GtkWidget *widget, { GtkPopover *popover = GTK_POPOVER (widget); GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + GtkStyleContext *context; + GtkBorder border; + cairo_t *cr; gtk_widget_snapshot_child (widget, priv->contents_widget, snapshot); + + cr = gtk_snapshot_append_cairo (snapshot, + &GRAPHENE_RECT_INIT ( + 0, 0, + gtk_widget_get_width (widget), + gtk_widget_get_height (widget) + )); + + /* Clip to the arrow shape */ + cairo_save (cr); + + gtk_popover_apply_tail_path (popover, cr); + + cairo_clip (cr); + + context = gtk_widget_get_style_context (widget); + gtk_style_context_save_to_node (context, priv->arrow_node); + gtk_style_context_get_border (context, &border); + + /* Render the arrow background */ + gtk_render_background (context, cr, + 0, 0, + gtk_widget_get_width (widget), + gtk_widget_get_height (widget)); + + /* Render the border of the arrow tip */ + if (border.bottom > 0) + { + GdkRGBA *border_color; + + gtk_style_context_get (context, "border-color", &border_color, NULL); + gtk_popover_apply_tail_path (popover, cr); + gdk_cairo_set_source_rgba (cr, border_color); + + cairo_set_line_width (cr, border.bottom + 1); + cairo_stroke (cr); + gdk_rgba_free (border_color); + } + + cairo_restore (cr); + cairo_destroy (cr); + gtk_style_context_restore (context); } static void @@ -1061,6 +1508,18 @@ gtk_popover_get_pointing_to (GtkPopover *popover, if (priv->has_pointing_to) *rect = priv->pointing_to; + else if (priv->relative_to) + { + graphene_rect_t r; + + if (!gtk_widget_compute_bounds (priv->relative_to, priv->relative_to, &r)) + return FALSE; + + rect->x = floorf (r.origin.x); + rect->y = floorf (r.origin.y); + rect->width = ceilf (r.size.width); + rect->height = ceilf (r.size.height); + } return priv->has_pointing_to; } @@ -1089,6 +1548,7 @@ gtk_popover_set_position (GtkPopover *popover, return; priv->position = position; + priv->final_position = position; g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_POSITION]); } diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss index 3a76203f46..aa601033f4 100644 --- a/gtk/theme/Adwaita/_common.scss +++ b/gtk/theme/Adwaita/_common.scss @@ -2170,19 +2170,21 @@ menuitem { * Popovers * ***************/ +popover.background { + background-color: transparent; + padding: 0px; +} + +popover.menu>arrow, popover>arrow { background-color: $popover_bg_color; border: 1px solid $borders_color; } -popover>contents.menu { - padding: 8px; - background-color: white; - margin: 0px; -} - popover>contents { - padding: 2px; + padding: 8px; + background-color: $popover_bg_color; + border: 1px solid $borders_color; margin: 0px; } @@ -4702,7 +4704,3 @@ popover.emoji-completion contents row box { popover.emoji-completion .emoji:hover { background-color: $popover_hover_color; } - -popover.menu { - background-color: lightgray; -} diff --git a/gtk/theme/Adwaita/gtk-contained-dark.css b/gtk/theme/Adwaita/gtk-contained-dark.css index d5b09205c2..fe1d6dfb2a 100644 --- a/gtk/theme/Adwaita/gtk-contained-dark.css +++ b/gtk/theme/Adwaita/gtk-contained-dark.css @@ -869,11 +869,11 @@ menuitem check:dir(ltr), menuitem radio:dir(ltr) { margin-right: 7px; } menuitem check:dir(rtl), menuitem radio:dir(rtl) { margin-left: 7px; } /*************** Popovers * */ -popover > arrow { background-color: #353535; border: 1px solid #1b1b1b; } +popover.background { background-color: transparent; padding: 0px; } -popover > contents.menu { padding: 8px; background-color: white; margin: 0px; } +popover.menu > arrow, popover > arrow { background-color: #353535; border: 1px solid #1b1b1b; } -popover > contents { padding: 2px; margin: 0px; } +popover > contents { padding: 8px; background-color: #353535; border: 1px solid #1b1b1b; margin: 0px; } popover > contents.background { border-radius: 9px; background-color: #353535; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); } @@ -1888,7 +1888,7 @@ colorswatch overlay:hover { box-shadow: inset 0 1px rgba(255, 255, 255, 0.4), in colorswatch overlay:backdrop, colorswatch overlay:backdrop:hover { border-color: #1b1b1b; box-shadow: none; } -colorswatch#add-color-button { border-radius: 5px 5px 0 0; } +colorswatch#add-color-button { border-radius: 5px 0 0 5px; } colorswatch#add-color-button:only-child { border-radius: 5px; } diff --git a/gtk/theme/Adwaita/gtk-contained.css b/gtk/theme/Adwaita/gtk-contained.css index 6e4fc8fde6..3c50e150fb 100644 --- a/gtk/theme/Adwaita/gtk-contained.css +++ b/gtk/theme/Adwaita/gtk-contained.css @@ -877,11 +877,11 @@ menuitem check:dir(ltr), menuitem radio:dir(ltr) { margin-right: 7px; } menuitem check:dir(rtl), menuitem radio:dir(rtl) { margin-left: 7px; } /*************** Popovers * */ -popover > arrow { background-color: #f6f5f4; border: 1px solid #cdc7c2; } +popover.background { background-color: transparent; padding: 0px; } -popover > contents.menu { padding: 8px; background-color: white; margin: 0px; } +popover.menu > arrow, popover > arrow { background-color: #f6f5f4; border: 1px solid #cdc7c2; } -popover > contents { padding: 2px; margin: 0px; } +popover > contents { padding: 8px; background-color: #f6f5f4; border: 1px solid #cdc7c2; margin: 0px; } popover > contents.background { border-radius: 9px; background-color: #f6f5f4; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); } @@ -1904,7 +1904,7 @@ colorswatch overlay:hover { box-shadow: inset 0 1px rgba(255, 255, 255, 0.4), in colorswatch overlay:backdrop, colorswatch overlay:backdrop:hover { border-color: rgba(0, 0, 0, 0.3); box-shadow: none; } -colorswatch#add-color-button { border-radius: 5px 5px 0 0; } +colorswatch#add-color-button { border-radius: 5px 0 0 5px; } colorswatch#add-color-button:only-child { border-radius: 5px; } -- 2.30.2